﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using sysenum = System.Enum;
using Newtonsoft.Json;
using System.Collections.Concurrent;
using System.Collections;
using JESAI.Core.Extensions;
using JESAI.Core.Util.Reflection;
using JESAI.Core.Util.Extensions.Strings;

namespace JESAI.Core.Util.Helpers
{
    /// <summary>
    /// 映射器帮助类
    /// </summary>
    public static class MapperHelper
    {
        /// <summary>
        /// 将源对象映射到目标对象
        /// </summary>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <typeparam name="TDestination">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static TDestination Map<TSource, TDestination>(TSource source) where TDestination : new()
        {
            if (source is null)
                throw new ArgumentNullException(nameof(source));
            var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);

            var destinationProperties = TypeReflections.TypeCacheManager.GetTypeProperties(destinationType);
            var sourceProperties = TypeReflections.TypeCacheManager.GetTypeProperties(sourceType)
                .Where(x => destinationProperties.Any(_ => _.Name.EqualsIgnoreCase(x.Name)))
                .ToArray();

            var result = new TDestination();

            if (destinationProperties.Length > 0)
            {
                foreach (var destinationProperty in destinationProperties)
                {
                    var sourceProperty = sourceProperties.FirstOrDefault(x => x.Name.EqualsIgnoreCase(destinationProperty.Name));
                    if (sourceProperty == null)
                        continue;

                    var propGetter = sourceProperty.GetValueGetter();
                    if (propGetter != null)
                        destinationProperty.GetValueSetter()?.Invoke(result, propGetter.Invoke(source));
                }
            }

            return result;
        }

        /// <summary>
        /// 将源对象映射到目标对象
        /// </summary>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <typeparam name="TDestination">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="propertiesToMap">属性映射数组，可指定映射部分属性</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static TDestination MapWith<TSource, TDestination>(TSource source, params string[] propertiesToMap) where TDestination : new()
        {
            if (source is null)
                throw new ArgumentNullException(nameof(source));
            var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);

            var destinationProperties = TypeReflections.TypeCacheManager.GetTypeProperties(destinationType)
                .Where(x => propertiesToMap.Any(_ => string.Equals(_, x.Name, StringComparison.OrdinalIgnoreCase)))
                .ToArray();
            var sourceProperties = TypeReflections.TypeCacheManager.GetTypeProperties(sourceType)
                .Where(x => propertiesToMap.Any(_ => _.EqualsIgnoreCase(x.Name)))
                .ToArray();

            var result = new TDestination();

            if (destinationProperties.Length > 0)
            {
                foreach (var destinationProperty in destinationProperties)
                {
                    var sourceProperty = sourceProperties.FirstOrDefault(x => x.Name.EqualsIgnoreCase(destinationProperty.Name));
                    if (sourceProperty == null || !sourceProperty.CanRead || !destinationProperty.CanWrite)
                        continue;

                    var propGetter = sourceProperty.GetValueGetter();
                    if (propGetter != null)
                        destinationProperty.GetValueSetter()?.Invoke(result, propGetter.Invoke(source));
                }
            }

            return result;
        }

        /// <summary>
        /// 将源对象映射到目标对象
        /// </summary>
        /// <typeparam name="TSource">源类型</typeparam>
        /// <typeparam name="TDestination">目标类型</typeparam>
        /// <param name="source">源对象</param>
        /// <param name="propertiesNoMap">忽略属性映射数组，忽略指定映射部分属性</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static TDestination MapWithout<TSource, TDestination>(TSource source, params string[] propertiesNoMap) where TDestination : new()
        {
            if (source is null)
                throw new ArgumentNullException(nameof(source));
            var sourceType = typeof(TSource);
            var destinationType = typeof(TDestination);

            var destinationProperties = TypeReflections.TypeCacheManager.GetTypeProperties(destinationType)
                .Where(x => !propertiesNoMap.Any(_ => string.Equals(_, x.Name, StringComparison.OrdinalIgnoreCase)))
                .ToArray();
            var sourceProperties = TypeReflections.TypeCacheManager.GetTypeProperties(sourceType)
                .Where(x => !destinationProperties.Any(_ => _.Name.EqualsIgnoreCase(x.Name)))
                .ToArray();

            var result = new TDestination();

            if (destinationProperties.Length > 0)
            {
                foreach (var destinationProperty in destinationProperties)
                {
                    var sourceProperty = sourceProperties.FirstOrDefault(x => x.Name.EqualsIgnoreCase(destinationProperty.Name));
                    if (sourceProperty == null || !sourceProperty.CanRead || !destinationProperty.CanWrite)
                        continue;

                    var propGetter = sourceProperty.GetValueGetter();
                    if (propGetter != null)
                        destinationProperty.GetValueSetter()?.Invoke(result, propGetter.Invoke(source));
                }
            }

            return result;
        }

        /// <summary>
        /// Mapper时用的缓存字典,内部封装了 ConcurrentDictionary&lt;object, object>，由 containsRepeatReference 决定是否使用内部的字典
        /// </summary>
        public class CacheDictionary
        {
            private readonly ConcurrentDictionary<(Type from, Type dest, object obj), object> dic;
            private readonly bool containsRepeatReference = true;
            public CacheDictionary(bool containsRepeatReference, int capacity = 1024)
            {
                this.containsRepeatReference = containsRepeatReference;
                if (containsRepeatReference) dic = new ConcurrentDictionary<(Type from, Type dest, object obj), object>(1, capacity);
            }

            public bool ContainsKey((Type from, Type dest, object obj) key)
            {
                if (!containsRepeatReference) return false;
                return dic.ContainsKey(key);
            }

            public void Add((Type from, Type dest, object obj) key, object value)
            {
                if (!containsRepeatReference) return;
                dic.TryAdd(key, value);
            }

            public object get_Item((Type from, Type dest, object obj) obj)
            {
                if (!containsRepeatReference) return null;
                return dic.ContainsKey(obj) ? dic[obj] : null;
            }

            public void set_Item((Type from, Type dest, object obj) key, object val)
            {
                if (!containsRepeatReference) return;
                dic.TryAdd(key, val);
            }
        }
        class Wrapper
        {
            public Func<object, CacheDictionary, bool, object[], object> Method { set; get; }
            public object Copy(object obj, CacheDictionary dic, bool null2Default, object[] args)
                => Method(obj, dic, null2Default, args);
        }

        static ConcurrentDictionary<(Type from, Type dest), Wrapper> _cache = new ConcurrentDictionary<(Type from, Type dest), Wrapper>();
        private static readonly TimeSpan timeSpan = TimeSpan.FromHours(8);
        private static List<TypeCode> _baseTypes = new List<TypeCode>
        {
            TypeCode.Byte,TypeCode.SByte,
            TypeCode.Int16,TypeCode.UInt16,
            TypeCode.Int32,TypeCode.UInt32,
            TypeCode.Int64,TypeCode.UInt64,
            TypeCode.Single,TypeCode.Double,TypeCode.Decimal,
            TypeCode.Char,TypeCode.Boolean,TypeCode.String,TypeCode.DateTime
        };
        private static Func<object, CacheDictionary, bool, object[], object> _baseConvert((Type from, Type dest) cacheKey)
        {
            var typeCode = cacheKey.dest.GetTypeCode();
            var isNullAble = cacheKey.dest.IsNullable();
            if (isNullAble) typeCode = cacheKey.dest.GenericTypeArguments[0].GetTypeCode();
            var fromClassFullName = cacheKey.from.GetClassFullName();
            var destClassFullName = cacheKey.dest.GetClassFullName();
            Func<bool, object> nullFunc = (null2Default) => null2Default ?
                                    (cacheKey.dest.GetDefault())
                                    :
                                    throw new Exception($"无法将 null or dbnull 转换到 [{destClassFullName}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destClassFullName}] 的默认值.");
            switch (typeCode)
            {
                case TypeCode.Byte:
                    {
                        return wraperFunc(obj => Convert.ToByte(obj));
                    }
                case TypeCode.SByte:
                    {
                        return wraperFunc(obj => Convert.ToSByte(obj));
                    }
                case TypeCode.Int16:
                    {
                        return wraperFunc(obj => Convert.ToInt16(obj));
                    }
                case TypeCode.UInt16:
                    {
                        return wraperFunc(obj => Convert.ToUInt16(obj));
                    }
                case TypeCode.Int32:
                    {
                        return wraperFunc(obj => Convert.ToInt32(obj));
                    }
                case TypeCode.UInt32:
                    {
                        return wraperFunc(obj => Convert.ToUInt32(obj));
                    }
                case TypeCode.Int64:
                    {
                        return wraperFunc(obj => Convert.ToInt64(obj));
                    }
                case TypeCode.UInt64:
                    {
                        return wraperFunc(obj => Convert.ToUInt64(obj));
                    }
                case TypeCode.Single:
                    {
                        return wraperFunc(obj => Convert.ToSingle(obj));
                    }
                case TypeCode.Double:
                    {
                        return wraperFunc(obj => Convert.ToDouble(obj));
                    }
                case TypeCode.Decimal:
                    {
                        return wraperFunc(obj => Convert.ToDecimal(obj));
                    }
                case TypeCode.Char:
                    {
                        return wraperFunc(obj => Convert.ToChar(obj));
                    }
                case TypeCode.Boolean:
                    {
                        return wraperFunc(obj => Convert.ToBoolean(obj));
                    }
                case TypeCode.String:
                    {
                        return wraperFunc(obj => Convert.ToString(obj));
                    }
                case TypeCode.DateTime:
                    {
                        return wraperFunc(obj => Convert.ToDateTime(obj));
                    }
                default:
                    {
                        if (isNullAble) return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception($"无法从:[{fromClassFullName}] 转换到 [{destClassFullName}]");
                        throw new Exception($"无法从:[{fromClassFullName}] 转换到 [{destClassFullName}]");
                    }
            }

            Func<object, CacheDictionary, bool, object[], object> wraperFunc(Func<object, object> actFunc)
            {
                if (isNullAble) return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : actFunc(obj);
                else return (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : actFunc(obj);
            }
        }

        /// <summary>
        /// 两个poco类之间的转换,忽略父子关系,基于编译表达式实现
        /// </summary>
        /// <param name="fromType">原类型</param>
        /// <param name="destType">目标类型</param>
        /// <param name="obj">原实例</param>
        /// <param name="containsRepeatReference">是否考虑引用关系</param>
        /// <param name="null2Default">是否将null值转换为默认值,而不是抛出异常</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="Exception"></exception>
        public static object Mapper(Type fromType, Type destType, object obj, bool containsRepeatReference = true, bool null2Default = true)
        {
            if (fromType == null) throw new ArgumentNullException(nameof(fromType));
            if (destType == null) throw new ArgumentNullException(nameof(destType));
            //Console.WriteLine($"fromType destType IsNullable: {fromType.GetClassFullName()} => {destType.GetClassFullName()}");
            //先简单判断
            if (obj.IsNullOrDBNull())
            {
                if (!destType.IsValueType) return null;
                if (null2Default) return destType.GetDefault();
                throw new Exception($"无法将 null or dbnull 转换到 [{destType.GetClassFullName()}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destType.GetClassFullName()}] 的默认值.");
            }

            var cacheKey = (fromType, destType);
            if (_cache.ContainsKey(cacheKey))
            {
                return _cache[cacheKey].Method.Invoke(obj, new CacheDictionary(containsRepeatReference), null2Default, null);
            }
            var func = _cache.GetOrAdd(cacheKey, type =>
            {
                var tmpCache = new Dictionary<(Type from, Type dest), Wrapper>();
                var func = GetMapperMethod(cacheKey, tmpCache);
                if (tmpCache.Count > 0)
                {
                    foreach (var item in tmpCache)
                    {
                        _cache.TryAdd(item.Key, item.Value);
                    }
                }
                return func;
            });
            return func.Method(obj, new CacheDictionary(containsRepeatReference), null2Default, null);

            #region GetMapperMethod
            Wrapper GetMapperMethod((Type from, Type dest) cacheKey, Dictionary<(Type from, Type dest), Wrapper> tmpCache)
            {
                if (_cache.ContainsKey(cacheKey)) return _cache[cacheKey];
                if (tmpCache.ContainsKey(cacheKey)) return tmpCache[cacheKey];
                var wrapper = new Wrapper();
                tmpCache.Add(cacheKey, wrapper);

                //类型相同,无需转换
                if (cacheKey.from == cacheKey.dest)
                {
                    wrapper.Method = (obj, dic, null2Default, args) => obj;
                }

                var destDefault = cacheKey.dest.GetDefault();
                var fromClassFullName = cacheKey.from.GetClassFullName();
                var destClassFullName = cacheKey.dest.GetClassFullName();
                Func<bool, object> nullFunc = (null2Default) => null2Default ?
                                    destDefault
                                    :
                                    throw new Exception($"无法将 null or dbnull 转换到 [{destClassFullName}]!也可以将 {nameof(null2Default)}  设置为true,以避免报错,但这将取 [{destClassFullName}] 的默认值.");

                #region nullable 和 非nullable互转
                //int? => int
                if (cacheKey.from.IsNullable())
                {
                    if (!cacheKey.dest.IsNullable())
                    {
                        if (cacheKey.from.GenericTypeArguments[0] == cacheKey.dest)
                        {
                            //int? => int
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : obj;
                            return wrapper;
                        }
                    }
                }
                //int => int?
                if (!cacheKey.from.IsNullable())
                {
                    if (cacheKey.dest.IsNullable())
                    {
                        if (cacheKey.dest.GenericTypeArguments[0] == cacheKey.from)
                        {
                            //int => int?
                            wrapper.Method = (obj, dic, null2Default, args) => obj;
                            return wrapper;
                        }
                    }
                }
                #endregion

                //基础类型
                var from = cacheKey.from;
                if (cacheKey.from.IsNullable()) from = cacheKey.from.GenericTypeArguments[0];
                var dest = cacheKey.dest;
                if (cacheKey.dest.IsNullable()) dest = cacheKey.dest.GenericTypeArguments[0];

                #region 其他 => string
                if (cacheKey.dest == typeof(string))
                {
                    //DateTime? => string 或 DateTime => string
                    if (from == typeof(DateTime))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                            (obj.IsNullOrDBNull() ? null : ((DateTime?)obj)?.ToString())
                            : ((DateTime?)obj)?.ToString(args.FirstOrDefault().ToString());
                        return wrapper;
                    }
                    //DateTimeOffset? => string 或 DateTimeOffset => string
                    if (from == typeof(DateTimeOffset))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                            (obj.IsNullOrDBNull() ? null : ((DateTimeOffset?)obj)?.ToString())
                            : ((DateTimeOffset?)obj)?.ToString(args.FirstOrDefault().ToString());
                        return wrapper;
                    }
#if NET6
                    //DateOnly? => string 或 DateOnly => string
                    if (from == typeof(DateOnly))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                            (obj.IsNullOrDBNull() ? null : ((DateOnly?)obj)?.ToString())
                            : ((DateOnly?)obj)?.ToString(args.FirstOrDefault().ToString());
                        return wrapper;
                    }
                    //TimeOnly? => string 或 TimeOnly => string
                    if (from == typeof(TimeOnly))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                            (obj.IsNullOrDBNull() ? null : ((TimeOnly?)obj)?.ToString())
                            : ((TimeOnly?)obj)?.ToString(args.FirstOrDefault().ToString());
                        return wrapper;
                    }
#endif
                    //Guid? => string 或 Guid => string
                    if (from == typeof(Guid))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => args.IsNullOrEmpty() ?
                            (obj.IsNullOrDBNull() ? null : ((Guid?)obj).ToString())
                            : ((Guid?)obj)?.ToString(args.FirstOrDefault().ToString());
                        return wrapper;
                    }
                    wrapper.Method = (obj, dic, null2Default, args) => obj?.ToString();
                    return wrapper;
                }
#endregion
                #region 其他 => ValueType
                if (dest.IsValueType)
                {
                    #region 其他 => enum
                    if (dest.IsEnum)
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : sysenum.Parse(dest, obj?.ToString(), true);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : sysenum.Parse(dest, obj?.ToString(), true);
                        }
                        return wrapper;
                    }
                    #endregion
                    #region 字符串转日期
                    //string => DateTime 或 string => DateTime?
                    if (from == typeof(string) && dest == typeof(DateTime))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                null
                                : (args.IsNullOrEmpty() ?
                                    DateTime.Parse(obj.ToString())
                                    : DateTime.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                nullFunc(null2Default)
                                : (args.IsNullOrEmpty() ?
                                    DateTime.Parse(obj.ToString())
                                    : DateTime.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        return wrapper;
                    }
                    //string => DateTimeOffset 或 string => DateTimeOffset?
                    if (from == typeof(string) && dest == typeof(DateTimeOffset))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                null
                                : (args.IsNullOrEmpty() ?
                                    DateTimeOffset.Parse(obj.ToString())
                                    : DateTimeOffset.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                nullFunc(null2Default)
                                : (args.IsNullOrEmpty() ?
                                    DateTimeOffset.Parse(obj.ToString())
                                    : DateTimeOffset.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        return wrapper;
                    }
#if NET6
                    //string => DateOnly
                    if (from == typeof(string) && dest == typeof(DateOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                null :
                                (args.IsNullOrEmpty() ?
                                    DateOnly.Parse(obj.ToString())
                                    : DateOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                nullFunc(null2Default)
                                : (args.IsNullOrEmpty() ?
                                    DateOnly.Parse(obj.ToString())
                                    : DateOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        return wrapper;
                    }
                    //string => TimeOnly
                    if (from == typeof(string) && dest == typeof(TimeOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                null
                                : (args.IsNullOrEmpty() ?
                                    TimeOnly.Parse(obj.ToString())
                                    : TimeOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ?
                                nullFunc(null2Default)
                                : (args.IsNullOrEmpty() ?
                                    TimeOnly.Parse(obj.ToString())
                                    : TimeOnly.ParseExact(obj.ToString(), args[0].ToString(), null));
                        }
                        return wrapper;
                    }
#endif
                    #endregion
#if NET6
                    #region DateTime/DateTimeOffset/TimeSpan => DateOnly/TimeOnly
                    //DateTime => DateOnly
                    if (from == typeof(DateTime) && dest == typeof(DateOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : DateOnly.FromDateTime((DateTime)obj);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : DateOnly.FromDateTime((DateTime)obj);
                        }
                        return wrapper;
                    }
                    //DateTimeOffset => DateOnly
                    if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : DateOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : DateOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                        }
                        return wrapper;
                    }
                    //TimeSpan => DateOnly
                    if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception("无法从TimeSpan转到DateOnly!");
                        }
                        else
                        {
                            throw new Exception("无法从TimeSpan转到DateOnly!");
                        }
                        return wrapper;
                    }
                    //DateTime => TimeOnly
                    if (from == typeof(DateTime) && dest == typeof(TimeOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromDateTime((DateTime)obj);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromDateTime((DateTime)obj);
                        }
                        return wrapper;
                    }
                    //DateTimeOffset => TimeOnly
                    if (from == typeof(DateTimeOffset) && dest == typeof(TimeOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromDateTime(((DateTimeOffset)obj).DateTime);
                        }
                        return wrapper;
                    }
                    //TimeSpan => TimeOnly
                    if (from == typeof(TimeSpan) && dest == typeof(TimeOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : TimeOnly.FromTimeSpan((TimeSpan)obj);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : TimeOnly.FromTimeSpan((TimeSpan)obj);
                        }
                        return wrapper;
                    }
                    #endregion
                    #region DateOnly/TimeOnly => DateTime/DateTimeOffset/TimeSpan
                    //DateOnly => DateTime
                    if (from == typeof(DateOnly) && dest == typeof(DateTime))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day);
                        }
                        return wrapper;
                    }
                    //DateOnly => DateTimeOffset
                    if (from == typeof(DateOnly) && dest == typeof(DateTimeOffset))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day, 0, 0, 0, timeSpan);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(((DateOnly)obj).Year, ((DateOnly)obj).Month, ((DateOnly)obj).Day, 0, 0, 0, timeSpan);
                        }
                        return wrapper;
                    }
                    //DateOnly => TimeSpan
                    if (from == typeof(DateTimeOffset) && dest == typeof(DateOnly))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : throw new Exception("无法从DateOnly转到TimeSpan!");
                        }
                        else
                        {
                            throw new Exception("无法从DateOnly转到TimeSpan!");
                        }
                        return wrapper;
                    }
                    //TimeOnly => DateTime
                    if (from == typeof(TimeOnly) && dest == typeof(DateTime))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                        }
                        return wrapper;
                    }
                    //TimeOnly => DateTimeOffset
                    if (from == typeof(TimeOnly) && dest == typeof(DateTimeOffset))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond, timeSpan);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(1970, 1, 1, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond, timeSpan);
                        }
                        return wrapper;
                    }
                    //TimeOnly => TimeSpan
                    if (from == typeof(TimeOnly) && dest == typeof(TimeSpan))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new TimeSpan(0, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new TimeSpan(0, ((TimeOnly)obj).Hour, ((TimeOnly)obj).Minute, ((TimeOnly)obj).Second, ((TimeOnly)obj).Millisecond);
                        }
                        return wrapper;
                    }
                              #endregion
#endif
                    #region DateTimeOffset 转 DateTime
                    if (from == typeof(DateTimeOffset) && dest == typeof(DateTime))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : ((DateTimeOffset)obj).DateTime;
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : ((DateTimeOffset)obj).DateTime;
                        }
                        return wrapper;
                    }
                    #endregion
                    #region DateTime 转 DateTimeOffset
                    if (from == typeof(DateTime) && dest == typeof(DateTimeOffset))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset((DateTime)obj);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset((DateTime)obj);
                        }
                        return wrapper;
                    }
                    #endregion
                    #region TimeSpan 转 DateTime DateTimeOffset
                    if (from == typeof(TimeSpan) && dest == typeof(DateTime))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTime(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTime(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds);
                        }
                        return wrapper;
                    }
                    if (from == typeof(TimeSpan) && dest == typeof(DateTimeOffset))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : new DateTimeOffset(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds, timeSpan);
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : new DateTimeOffset(1970, 01, 01, ((TimeSpan)obj).Hours, ((TimeSpan)obj).Minutes, ((TimeSpan)obj).Seconds, ((TimeSpan)obj).Milliseconds, timeSpan);
                        }
                        return wrapper;
                    }
                    #endregion

                    #region 字符串转bool
                    if (from == typeof(string) && dest == typeof(bool))
                    {
                        var arr = new string[] { "OK", "YES", "TRUE", "1", "是" };
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : arr.Contains(obj.ToString());
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : arr.Any(i => string.Equals(i, obj?.ToString(), StringComparison.OrdinalIgnoreCase));
                        }
                        return wrapper;
                    }
                    #endregion
                    #region 字符串转guid
                    if (from == typeof(string) && dest == typeof(Guid))
                    {
                        if (cacheKey.dest.IsNullable())
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : Guid.Parse(obj.ToString());
                        }
                        else
                        {
                            wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : Guid.Parse(obj.ToString());
                        }
                        return wrapper;
                    }
                    #endregion
                    #region 可以使用Convert.ToXXX的转换
                    var typeCode = dest.GetTypeCode();
                    if (_baseTypes.Contains(typeCode))
                    {
                        wrapper.Method = _baseConvert(cacheKey);
                        return wrapper;
                    }
                    #endregion
                    #region 其他 使用 JsonConvert 实现
                    if (cacheKey.dest.IsNullable())
                    {
                        //Console.WriteLine($"JsonConvert IsNullable: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}");
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), cacheKey.dest);
                    }
                    else
                    {
                        //Console.WriteLine($"JsonConvert: {cacheKey.from.GetClassFullName()} => {cacheKey.dest.GetClassFullName()}");
                        wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? nullFunc(null2Default) : JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), cacheKey.dest);
                    }
                    return wrapper;
                    #endregion
                }
#endregion
                #region json字符串反序列化
                if (cacheKey.from == typeof(string) && cacheKey.dest.IsClass)
                {
                    wrapper.Method = (obj, dic, null2Default, args) => obj.IsNullOrDBNull() ? null : JsonConvert.DeserializeObject(obj.ToString(), cacheKey.dest);
                    return wrapper;
                }
                #endregion

                var destReflect = dest.GetClassGenericFullName();
                var fromReflect = from.GetClassGenericFullName();

                #region => List<T>
                if (destReflect.Name == "System.Collections.Generic.List<T>" || destReflect.Name == "System.Collections.Generic.IList<T>" || destReflect.Name == "System.Collections.Generic.ICollection<T>")
                {
                    //IEnumerable<T> => List<T>
                    //Array<T> => List<T>
                    if (from==typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type))
                    {
                        GetMapperMethod_IEnumerable_List();
                        return wrapper;
                    }
                    //IEnumerable<T> => List<T2>
                    //Array<T> => List<T2>
                    Type enumerable = null;
                    if (from.IsInterface && from.GetClassFullName().StartsWith("System.Collections.Generic.IEnumerable<"))
                    {
                        enumerable = from;
                    }
                    else
                    {
                        enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                    }
                    if (enumerable != null)
                    {
                        GetMapperMethod_IEnumerable_List2(enumerable.GetClassGenericFullName().GenericTypes.First().type, destReflect.GenericTypes.First().type);
                        return wrapper;
                    }
                    throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
                }
                #endregion
                #region => IEnumerable<T>
                if (destReflect.Name == "System.Collections.Generic.IEnumerable<T>")
                {
                    //{IEnumerable<T>} => IEnumerable<T>
                    if (from==typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type))
                    {
                        wrapper.Method = (obj, dic, null2Default, args) => obj;
                        return wrapper;
                    }
                    //{IEnumerable<T>} => IEnumerable<T2>
                    Type enumerable = null;
                    if (from.IsInterface && from.GetClassFullName().StartsWith("System.Collections.Generic.IEnumerable<"))
                    {
                        enumerable = from;
                    }
                    else
                    {
                        enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                    }
                    if (enumerable != null)
                    {
                        GetMapperMethod_IEnumerable_List2(enumerable.GetClassGenericFullName().GenericTypes.First().type, destReflect.GenericTypes.First().type);
                        return wrapper;
                    }
                    throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
                }
                #endregion
                #region => Array<T>
                if (dest.IsArray)
                {
                    if (from==typeof(IEnumerable<>).MakeGenericType(dest.GetElementType()))
                    {
                        //{IEnumerable<T>} => Array<T>
                        var method = typeof(Enumerable).GetMethod("ToArray");
                        Func<object, object> func = obj => method.Invoke(null, new object[] { obj });
                        wrapper.Method = (obj, dic, null2Default, args) => obj == null ? null :
                            (dic.ContainsKey((from, dest, obj)) ? dic.get_Item((from, dest, obj)) : func(obj));
                        return wrapper;
                    }
                    var enumerable = from.GetInterfaces().Where(i => i.Name == "IEnumerable`1").FirstOrDefault();
                    if (enumerable != null)
                    {
                        //{IEnumerable<T>} => Array<T2>
                        GetMapperMethod_IEnumerable_Array2(enumerable.GetClassGenericFullName().GenericTypes.First().type, dest.GetElementType());
                        return wrapper;
                    }
                    throw new Exception($"无法Mapper: [{from.GetClassFullName()}] => [{dest.GetClassFullName()}]!");
                }
                #endregion
                GetMapperMethod_Poco();
                return wrapper;

                #region poco
                void GetMapperMethod_Poco()
                {
                    var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp(cacheKey);
                    var ctor = dest.GetConstructor(new Type[0]);
                    if (ctor == null)
                        throw new Exception($"类型[{dest.GetClassFullName()}]必须有无参的构造函数!");
                    var newExp = Expression.New(ctor);
                    var localRes = Expression.Variable(dest, "localRes");
                    var localObj = Expression.Variable(from, "localObj");
                    var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, from));

                    var assignRes = Expression.Assign(localRes, newExp);
                    var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                    var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                    var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                        Expression.New(valueCtor, Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj),
                    localRes});

                    var argTypes = new[] { typeof(DateTime), typeof(DateTime?), typeof(DateTimeOffset), typeof(DateTimeOffset?), typeof(Guid), typeof(Guid?) };

                    #region 公共属性
                    var destProps = dest.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(i => i.CanWrite).ToArray();
                    var fromProps = from.GetProperties(BindingFlags.Public | BindingFlags.Instance);
                    var assigns = new List<BinaryExpression>();
                    for (int i = 0; i < destProps.Length; i++)
                    {
                        var destProp = destProps[i];
                        var propName = destProp.Name;
                        var fromProp = fromProps.FirstOrDefault(i => i.Name == propName);
                        if (fromProp != null)
                        {
                            if (fromProp.PropertyType == destProp.PropertyType)
                                assigns.Add(Expression.Assign(Expression.Property(localRes, propName), Expression.Property(localObj, propName)));
                            else
                            {
                                var innerMapper = GetMapperMethod((fromProp.PropertyType, destProp.PropertyType), tmpCache);
                                //转换参数
                                #region 其他类型转string
                                if (destProp.PropertyType == typeof(string))
                                {
                                    if (argTypes.Contains(fromProp.PropertyType))
                                    {
                                        //日期或guid转字符串
                                        var formatter = "";
                                        var converter = fromProp.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = destProp.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            formatter = converter.Args.FirstOrDefault().ToString();
                                        }
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, propName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Property(localObj, propName), typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatter }, typeof(object[])) }), destProp.PropertyType)));
                                    }
                                    else
                                    {
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, propName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Property(localObj, propName), typeof(object)), para_dic, para_null2Default, para_args }), destProp.PropertyType)));
                                    }
                                    continue;
                                }
                                #endregion

                                //字符串转日期或guid
                                #region 字符串转日期或guid
                                if (fromProp.PropertyType == typeof(string))
                                {
                                    if (argTypes.Contains(destProp.PropertyType))
                                    {
                                        //字符串转日期
                                        var formatter = "";
                                        var converter = fromProp.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = destProp.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            formatter = converter.Args.FirstOrDefault().ToString();
                                        }
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, propName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Property(localObj, propName), typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatter }, typeof(object[])) }), destProp.PropertyType)));
                                        continue;
                                    }
                                }
                                #endregion

                                assigns.Add(Expression.Assign(Expression.Property(localRes, propName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Property(localObj, propName), typeof(object)), para_dic, para_null2Default, para_args }), destProp.PropertyType)));
                            }
                        }
                    }
                    #endregion
                    #region 公共字段
                    var destFields = dest.GetFields(BindingFlags.Public | BindingFlags.Instance);
                    var fromFields = from.GetFields(BindingFlags.Public | BindingFlags.Instance);
                    for (int i = 0; i < destFields.Length; i++)
                    {
                        var destField = destFields[i];
                        var fieldName = destField.Name;
                        var fromField = fromFields.FirstOrDefault(i => i.Name == fieldName);
                        if (fromField != null)
                        {
                            if (fromField.FieldType == destField.FieldType)
                            {
                                assigns.Add(Expression.Assign(Expression.Field(localRes, fieldName), Expression.Field(localObj, fieldName)));
                            }
                            else
                            {
                                var innerMapper = GetMapperMethod((fromField.FieldType, destField.FieldType), tmpCache);
                                //转换参数
                                #region 其他类型转string
                                if (destField.FieldType == typeof(string))
                                {
                                    if (argTypes.Contains(fromField.FieldType))
                                    {
                                        //日期或guid转字符串
                                        var formatter = "";
                                        var converter = fromField.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = destField.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            formatter = converter.Args.FirstOrDefault().ToString();
                                        }
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, fieldName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Field(localObj, fieldName), typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatter }, typeof(object[])) }), destField.FieldType)));
                                    }
                                    else
                                    {
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, fieldName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Field(localObj, fieldName), typeof(object)), para_dic, para_null2Default, para_args }), destField.FieldType)));
                                    }
                                    continue;
                                }
                                #endregion

                                //字符串转日期或guid
                                #region 字符串转日期或guid
                                if (fromField.FieldType == typeof(string))
                                {
                                    if (argTypes.Contains(destField.FieldType))
                                    {
                                        //字符串转日期
                                        var formatter = "";
                                        var converter = fromField.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        if (converter == null)
                                        {
                                            converter = destField.GetCustomAttributes().FirstOrDefault(i => i.GetType() == typeof(MapperArgumentsAttribute)) as MapperArgumentsAttribute;
                                        }
                                        if (converter != null && converter.Args?.Length > 0)
                                        {
                                            formatter = converter.Args.FirstOrDefault().ToString();
                                        }
                                        assigns.Add(Expression.Assign(Expression.Property(localRes, fieldName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Field(localObj, fieldName), typeof(object)), para_dic, para_null2Default, Expression.Constant(new object[] { formatter }, typeof(object[])) }), destField.FieldType)));
                                        continue;
                                    }
                                }
                                #endregion

                                assigns.Add(Expression.Assign(Expression.Field(localRes, fieldName), Expression.Convert(Expression.Call(Expression.Constant(innerMapper), typeof(Wrapper).GetMethod("Copy"), new Expression[] { Expression.Convert(Expression.Field(localObj, fieldName), typeof(object)), para_dic, para_null2Default, para_args }), destField.FieldType)));
                            }
                        }
                    }
                    #endregion

                    var blocks = new List<Expression> {
                        ifnullExp,
                        //调试
                        //Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant($"haha2:{cacheKey.from.GetClassFullName()}=>{cacheKey.dest.GetClassFullName()}")),
                        ifCacheExp,
                        assignLocalObj,
                        assignRes,
                        addDic };
                    blocks.AddRange(assigns);
                    blocks.AddRange(new Expression[] { Expression.Goto(retLabel, localRes), retExp });
                    var block = Expression.Block(new ParameterExpression[] { localObj, localRes }, blocks);
                    var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                    wrapper.Method = finalExp.Compile();
                }
                #endregion
                #region IEnumerable<T> => List<T>
                void GetMapperMethod_IEnumerable_List()
                {
                    var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp(cacheKey);
                    var ienumtype = typeof(IEnumerable<>).MakeGenericType(destReflect.GenericTypes.First().type);
                    var localRes = Expression.Variable(dest, "localRes");
                    var localObj = Expression.Variable(ienumtype, "localObj");
                    var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, ienumtype));
                    var assignRes = Expression.Assign(localRes, Expression.Call(null, typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(destReflect.GenericTypes.First().type), localObj));

                    var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                    var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                    var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                        Expression.New(valueCtor, Expression.Constant(fromType), Expression.Constant(destType), para_obj),
                    localRes});

                    var blocks = new List<Expression> { ifnullExp,
                        ifCacheExp,
                        assignLocalObj,
                        assignRes,
                        addDic,
                        Expression.Goto(retLabel, localRes),
                        retExp
                    };
                    var block = Expression.Block(new ParameterExpression[] { localObj, localRes }, blocks);
                    var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                    wrapper.Method = finalExp.Compile();
                }
                #endregion
                #region IEnumerable<T> => List<T2> || IEnumerable<T2>
                void GetMapperMethod_IEnumerable_List2(Type fromElement, Type destElement)
                {
                    var newFromType = typeof(IEnumerable<>).MakeGenericType(fromElement);
                    var newDestType = typeof(List<>).MakeGenericType(destElement);

                    var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp((newFromType, newDestType));
                    var localRes = Expression.Variable(newDestType, "localRes");
                    var localObj = Expression.Variable(newFromType, "localObj");
                    var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, newFromType));

                    var ratorType = typeof(IEnumerator<>).MakeGenericType(fromElement);
                    //IEnumerator<Person> rator;
                    var ratorVar = Expression.Variable(ratorType, "rator");
                    //rator=obj2.GetEnumerator();
                    var assignRator = Expression.Assign(ratorVar, Expression.Call(localObj, newFromType.GetMethod("GetEnumerator")));

                    var newListExp = Expression.New(newDestType.GetConstructor(new Type[0]));
                    //res=new List<PersonDto>()
                    var assignRes = Expression.Assign(localRes, newListExp);
                    var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                    var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                    var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                        Expression.New(valueCtor, Expression.Constant(newFromType), Expression.Constant(newDestType), para_obj),
                    localRes});

                    var breakLabel = Expression.Label("break");

                    var innerMapper = GetMapperMethod((fromElement, destElement), tmpCache);

                    var moveNext = typeof(IEnumerator).GetMethod("MoveNext");

                    //while (rator.MoveNext())
                    var loopBody = Expression.Block(
                        Expression.IfThenElse(Expression.IsTrue(Expression.Call(ratorVar, moveNext)),
                            Expression.Block(
                                Expression.Call(//res.Add(innerMapper(rator.Current))
                    localRes
                                    , newDestType.GetMethod("Add", new Type[] { destElement })
                                    , Expression.Convert(Expression.Invoke(//innerMapper(rator.Current)
                                            Expression.MakeMemberAccess(Expression.Constant(innerMapper), typeof(Wrapper).GetMember("Method")[0])//wrapper.Method.Invoke()
                                            , Expression.Convert(Expression.Property(ratorVar, "Current"), typeof(object))//(object)rator.Current
                                            , para_dic
                                            , para_null2Default
                                            , para_args
                                        ), destElement)
                                )
                            )
                            , Expression.Goto(breakLabel))
                    );
                    var loopExp = Expression.Loop(loopBody, breakLabel);
                    var block = Expression.Block(
                        new ParameterExpression[] { localObj, localRes, ratorVar },
                        //调试
                        //Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }), Expression.Constant("haha")),
                        ifnullExp,
                        ifCacheExp,
                        assignLocalObj,
                        assignRator,
                        assignRes,
                        addDic,
                        loopExp,
                        Expression.Goto(retLabel, localRes),
                        retExp
                        );
                    var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                    wrapper.Method = finalExp.Compile();
                }
                #endregion
                #region IEnumerable<T> => Array<T2>
                void GetMapperMethod_IEnumerable_Array2(Type fromElement, Type destElement)
                {
                    var newFromType = typeof(IEnumerable<>).MakeGenericType(fromElement);
                    var newDestType = destElement.MakeArrayType();

                    var (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp) = GetCommonExp((newFromType, newDestType));
                    var localRes = Expression.Variable(newDestType, "localRes");
                    var localObj = Expression.Variable(newFromType, "localObj");
                    var assignLocalObj = Expression.Assign(localObj, Expression.TypeAs(para_obj, newFromType));

                    var countMethod = typeof(Enumerable).GetMethods().Where(i => i.Name == "Count").FirstOrDefault(i => i.GetParameters().Length == 1).MakeGenericMethod(fromElement);
                    var localCount = Expression.Variable(typeof(int), "count");
                    var assignCount = Expression.Assign(localCount, Expression.Call(null, countMethod, new Expression[] { localObj }));

                    var ratorType = typeof(IEnumerator<>).MakeGenericType(fromElement);
                    //IEnumerator<Person> rator;
                    var ratorVar = Expression.Variable(ratorType, "rator");
                    //rator=obj2.GetEnumerator();
                    var assignRator = Expression.Assign(ratorVar, Expression.Call(localObj, newFromType.GetMethod("GetEnumerator")));

                    var newArrayExp = Expression.NewArrayBounds(destElement, localCount);
                    //res=new PersonDto[](count)
                    var assignRes = Expression.Assign(localRes, newArrayExp);
                    var valueType = typeof(ValueTuple<,,>).MakeGenericType(typeof(Type), typeof(Type), typeof(object));
                    var valueCtor = valueType.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                    var addDic = Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("set_Item"), new Expression[] {
                        Expression.New(valueCtor, Expression.Constant(newFromType), Expression.Constant(newDestType), para_obj),
                    localRes});

                    var breakLabel = Expression.Label("break");

                    var innerMapper = GetMapperMethod((fromElement, destElement), tmpCache);

                    //for(var i=0;i<count;i++)
                    //int i;
                    var localVar_i = Expression.Variable(typeof(int), "i");
                    //i=0;
                    var loopInit = Expression.Assign(localVar_i, Expression.Constant(0));//i=0

                    var moveNext = typeof(IEnumerator).GetMethod("MoveNext");

                    //while (rator.MoveNext())
                    var loopBody = Expression.Block(
                        Expression.IfThenElse(Expression.IsTrue(Expression.Call(ratorVar, moveNext)),
                            Expression.Block(
                                Expression.Assign(Expression.ArrayAccess(localRes, localVar_i),
                                   Expression.Convert(Expression.Invoke(//innerMapper(obj2[i])
                                       Expression.MakeMemberAccess(Expression.Constant(innerMapper), typeof(Wrapper).GetMember("Method")[0])//wrapper.Method.Invoke()
                                        , Expression.Convert(Expression.Property(ratorVar, "Current"), typeof(object))//(object)rator.Current
                                        , para_dic
                                        , para_null2Default
                                        , para_args
                                    ), destElement)
                                ),
                                Expression.PostIncrementAssign(localVar_i)//i++;
                            )
                            , Expression.Goto(breakLabel))
                    );
                    var loopExp = Expression.Loop(loopBody, breakLabel);
                    var block = Expression.Block(
                        new ParameterExpression[] { localObj, localRes, ratorVar, localCount, localVar_i },
                        ifnullExp,
                        ifCacheExp,
                        assignLocalObj,
                        assignCount,
                        assignRator,
                        assignRes,
                        addDic,
                        loopInit,
                        loopExp,
                        Expression.Goto(retLabel, localRes),
                        retExp
                        );
                    var finalExp = Expression.Lambda<Func<object, CacheDictionary, bool, object[], object>>(block, para_obj, para_dic, para_null2Default, para_args);
                    wrapper.Method = finalExp.Compile();
                }
                #endregion
                #region GetCommonExp
                (ParameterExpression para_obj,
                                ParameterExpression para_dic,
                                ParameterExpression para_null2Default,
                                ParameterExpression para_args,
                                LabelTarget retLabel,
                                LabelExpression retExp,
                                ConditionalExpression ifnullExp,
                                ConditionalExpression ifCacheExp
                                ) GetCommonExp((Type from, Type dest) cacheKey)
                {
                    var para_obj = Expression.Parameter(typeof(object), "obj");//(object obj)
                    var para_dic = Expression.Parameter(typeof(CacheDictionary), "dic");//(dictionary<object,object> dic)
                    var para_null2Default = Expression.Parameter(typeof(bool), "null2Default");//(bool null2Default)
                    var para_args = Expression.Parameter(typeof(object[]), "args");//(object[] args)
                    var retLabel = Expression.Label(typeof(object), "ret");
                    var retExp = Expression.Label(retLabel, para_obj);
                    var ifnullExp = Expression.IfThen(Expression.Equal(para_obj, Expression.Constant(null)), Expression.Return(retLabel, para_obj));//if (i==null) return i;

                    var type = typeof(ValueTuple<,,>).MakeGenericType(new[] { typeof(Type), typeof(Type), typeof(object) });
                    var ctor = type.GetConstructor(new[] { typeof(Type), typeof(Type), typeof(object) });
                    //if (dic.ContainsKey((cacheKey.from,cacheKey.dest,obj))) return dic[dic.ContainsKey((cacheKey.from,cacheKey.dest,obj))];
                    var ifCacheExp = Expression.IfThen(
                        Expression.IsTrue(
                            Expression.Call(
                                para_dic,
                                typeof(CacheDictionary).GetMethod("ContainsKey"),
                                new Expression[] {
                                Expression.New(ctor, new Expression[] { Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj }) })),
                        Expression.Return(retLabel, Expression.Call(para_dic, typeof(CacheDictionary).GetMethod("get_Item"), Expression.New(ctor, new Expression[] { Expression.Constant(cacheKey.from), Expression.Constant(cacheKey.dest), para_obj }))));
                    return (para_obj, para_dic, para_null2Default, para_args, retLabel, retExp, ifnullExp, ifCacheExp);
                }
                #endregion

#endregion
            }
        }

        /// <summary>
        /// 注册指定 fromType => destType 的Mapper逻辑，如:
        /// <code>
        /// MapperHelper.RegisterMapperHander((typeof(Person), typeof(PersonDto)), (obj, dic, containsRepeatReference, null2Default) =>
        /// {
        ///     var key = (typeof(Person), typeof(PersonDto), obj);
        ///     if (dic.ContainsKey(key)) return dic.get_Item(key);
        ///     var res = new PersonDto();
        ///     dic.set_Item(key, res);
        ///     var person = obj as Person;
        ///     res.Id = person.Id + 1;
        ///     res.Name = person.Name + "Mapper";
        ///     return res;
        /// });
        /// var pers = new Person
        /// {
        ///     Id = 1,
        ///     Name = "小明"
        /// };
        /// var dto = pers.Mapper&lt;PersonDto>();
        /// dto.Id.ShouldBe(2);
        /// dto.Name.ShouldBe("小明Mapper");
        /// </code>
        /// </summary>
        /// <param name="type"></param>
        /// <param name="func"></param>
        public static void RegisterMapperHander((Type fromType, Type destType) type, Func<object, CacheDictionary, bool, object[], object> func)
        {
            if (type.fromType == null || type.destType == null) throw new ArgumentNullException("type.fromType|type.destType");
            if (func == null) throw new ArgumentNullException("func");
            _cache.TryAdd(type, new Wrapper
            {
                Method = func
            });
        }
    }
}